﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using VNet.VisiSuite.Common.Logger;

namespace VNet.VisiSuite.Controls
{
    /// <summary>
    /// Interaction logic for CircularProgessChart.xaml
    /// </summary>
    public partial class CircularProgessChart : UserControl
    {
        public class ProgressEvent : EventArgs
        {
            public enum ProgressStateEnum
            {
                LoadUnload,
                LoadUnloadToleranceStart,
                LoadUnloadOverRunStart
            }

            public ProgressEvent(int secs, ProgressStateEnum state)
            {
                Seconds = secs;
                EventType = state;
            }

            public int Seconds { get; set; }
            public ProgressStateEnum EventType { get; set; }
        }

        private class DataModel
        {
            public DataModel(int percentage, Brush background, Brush foreground)
            {
                Percentage = percentage;
                Background = background;
                Foreground = foreground;
            }
            public int Percentage { get; set; }
            public Brush Background{ get; set; }
            public Brush Foreground { get; set; }
        }

        public EventHandler ProgressUpdate;

        private int _seconds;
        private VNet.VisiSuite.Controls.CircularProgessChart.ProgressEvent.ProgressStateEnum _state;
        private List<DataModel> _models;
        private readonly DispatcherTimer _timer = new DispatcherTimer(DispatcherPriority.Render);
        private Stopwatch _stop_watch;
        private bool _needs_redraw;

        public void beginProgress(int seconds, List<int> percentages, List<Brush> backgrounds, List<Brush> foregrounds )
        {
            _seconds = seconds;
            _state = ProgressEvent.ProgressStateEnum.LoadUnload;
            _models = new List<DataModel>();
            for (int i = 0 ; i < percentages.Count; i ++ )
                _models.Add(new DataModel(percentages[i],backgrounds[i], foregrounds[i]));

            _timer.Stop();
            _timer.Tick += timer_Elapsed;
            _timer.Interval = new TimeSpan(0, 0, 1);

            FgBrush = Brushes.Green;
            BgStroke = Brushes.Transparent;
            BgBrush = Brushes.LightGray;
            Value = 0;

            _stop_watch = new Stopwatch();
            _timer.Start();
            _stop_watch.Start();

            onNeedsRedraw();

        }

        public void stop()
        {
            _timer.Stop();
            _timer.Tick -= timer_Elapsed;
            _stop_watch.Stop();
        }

        private void onNeedsRedraw()
        {
            // Prevent multiple redraws when multiple properties change.
            Dispatcher.BeginInvoke((Action)(() =>
            {
                if (_needs_redraw)
                {
                    _needs_redraw = false;
                    redraw();
                }
            }), DispatcherPriority.ContextIdle);
        }

        void redraw()
        {
            _needs_redraw = false;

            double elapsed = _stop_watch.ElapsedMilliseconds / 1000.0;

            if (elapsed > _seconds * 1.10)
            {
                FgBrush = Brushes.Transparent;
                BgStroke = Brushes.White;
                BgBrush = Brushes.Red;
                if (_state != ProgressEvent.ProgressStateEnum.LoadUnloadOverRunStart)
                {
                    _state = ProgressEvent.ProgressStateEnum.LoadUnloadOverRunStart;
                    ProgressUpdate(this, new ProgressEvent(Convert.ToInt32(Math.Round(_seconds * 1.10)) - _seconds, _state));
                }
            }
            else if (elapsed > _seconds)
            {
                FgBrush = Brushes.Yellow;
                FgBrush = Brushes.Red;
                if (_state != ProgressEvent.ProgressStateEnum.LoadUnloadToleranceStart)
                {
                    _state = ProgressEvent.ProgressStateEnum.LoadUnloadToleranceStart;
                    ProgressUpdate(this, new ProgressEvent(_seconds, _state));
                }
            }
            else
            {
                FgBrush = Brushes.Green;
            }

            Value = elapsed;
        }

        void timer_Elapsed(object sender, EventArgs e)
        {
            _needs_redraw = true;
            onNeedsRedraw();
        }

        /// <summary>
        /// Set the text color of the value in the center
        /// </summary>
        public Brush FontBrush {
            set {
                lblValue.Foreground = value;
            }
        }

        /// <summary>
        /// Set the color for the total pie
        /// </summary>
        public Brush BgBrush {
            set {
                bgCircle.Fill = value;                
            }
        }

        public Brush BgStroke
        {
            set
            {
                bgCircle.Stroke = value;
            }
        }

        /// <summary>
        /// Set the color for the hole
        /// </summary>
        public Brush HoleBrush {
            set {
                hole.Fill = value;
            }
        }

        /// <summary>
        /// Set the color for the fill for the sector
        /// </summary>
        public Brush FgBrush {
            set {
                    path.Fill = value;
            }
        }

        /// <summary>
        /// The value for this pie
        /// </summary>
        private double val=0;
        public double Value {
            get {
                return val;
            }

            set {
                val = value / (1.10 * _seconds);
                if (val > 1) val = 1; else if (val < 0) val = 0;

                try
                {
                    drawSector(val);
                }
                catch (Exception ex)
                {
                    FileLog.Error("CircularProgressChart - Value - ", ex);
                }

                lblValue.Content = "" + value.ToString("0") + "/" + _seconds;
                //lblValue.Content = "" + (val * 100) + "%";

                
            }
        }

        /// <summary>
        /// this method draws the sector based on the value
        /// </summary>
        /// <param name="value"></param>
        private void drawSector(double value){

            /*
             * The circular pie consists of three parts
             * a. the background
             * b. the pie / sector
             * c. the hole
             * 
             * The path object in the user control will be used to
             * create the pie/sector. Ellipses are used to create the other
             * two. This method draws a sector of a circle based on the value
             * passed into this function.
             * 
             * A sector has three parts -
             * a. a line segment from center to circumfrence
             * b. Arcs - parts of the circumfrence
             * c. a line segment from circumfrence to center
             */
            
            //Clean the path
            path.Dispatcher.Invoke(DispatcherPriority.Background, new Action(() =>
            {
                path.SetValue(Path.DataProperty, null);
                PathGeometry pg = new PathGeometry();
                PathFigure fig = new PathFigure();

                //if the value is zero, do nothing further
                if (value == 0) return;

                //a few variables for good measure
                double height = this.ActualHeight;
                double width = this.ActualWidth;
                double radius = height / 2;
                double theta = (360 * value) - 90;  // <--- the coordinate system is flipped with 0,0 at top left. Hence the -90
                double xC = radius;
                double yC = radius;

                //this is to ensure that the fill is complete, otherwise a slight
                //gap is left in the fill of the sector. By incrementing it by one
                //we fill that gap as the angle is now 361 deg (-90 for the coord sys inversion)
                if (value == 1) theta += 1;

                //finalPoint represents the point along the circumfrence after
                //which the sector ends and the line back to the center begins
                //use parametric equations for a circule to figure that out
                // <--- PI / 180 = 0.0174

                double finalPointX = xC + (radius * Math.Cos(theta * 0.0174));
                double finalPointY = yC + (radius * Math.Sin(theta * 0.0174));

                //Now we have calculated all the points we need to start drawing the
                //segments for the figure. Drawing a figure in WPF is like using a pencil
                //without lifting the tip. Each segment specifies an end point and
                //is connected to the previous segment's end point. 

                fig.StartPoint = new Point(radius, radius);                                         //start by placing the pencil's tip at the center of the circle
                LineSegment firstLine = new LineSegment(new Point(radius, 0), true);                //the first line segment goes vertically upwards (radius,0)
                fig.Segments.Add(firstLine);                                                        //draw that line segment

                //Now we have to draw the arc for this sector. The way drawing works in wpf,
                //in order to get a proper and coherent circumfrence, we need to draw separate
                //arcs everytime the sector exceeds a quarter of the circle. 

                if (value > 0.25)
                {                                                                 //if the sector will exceed the first quarter
                    ArcSegment firstQuart = new ArcSegment();                                       //first get ready to draw the first quarter's arc
                    firstQuart.Point = new Point(width, radius);                                    //being a quarter, it ends at (width,radius)
                    firstQuart.SweepDirection = SweepDirection.Clockwise;                           //we're drawing clockwise
                    firstQuart.IsStroked = true;                                                    //yes, it is stroked
                    firstQuart.Size = new Size(radius, radius);                                     //the size of this is for a circle with r = radius
                    fig.Segments.Add(firstQuart);                                                   //now draw this arc (fill property would make it a sector)
                }

                if (value > 0.5)
                {                                                                  //if the secotr will exceed the second quarter
                    ArcSegment secondQuart = new ArcSegment();                                      //get ready to draw the second quarter's arc too
                    secondQuart.Point = new Point(radius, height);                                  //..
                    secondQuart.SweepDirection = SweepDirection.Clockwise;                          //..
                    secondQuart.IsStroked = true;                                                   //..
                    secondQuart.Size = new Size(radius, radius);                                    //..
                    fig.Segments.Add(secondQuart);                                                  //draw the second quarter's arc
                }

                if (value > 0.75)
                {                                                                 //Similarly if the sector will exceed three quarters
                    ArcSegment thirdQuart = new ArcSegment();                                       //get ready to draw the third quarter's arc (starts at the end of the previous quarter)
                    thirdQuart.Point = new Point(0, radius);                                        //ends at (0,radius) 
                    thirdQuart.SweepDirection = SweepDirection.Clockwise;
                    thirdQuart.IsStroked = true;
                    thirdQuart.Size = new Size(radius, radius);
                    fig.Segments.Add(thirdQuart);                                                   //draw this arc
                }

                ArcSegment finalQuart = new ArcSegment();                                           //finally, once we're done with the quarter arcs
                finalQuart.Point = new Point(finalPointX, finalPointY);                             //let's get ready to draw the last arc/sector
                finalQuart.SweepDirection = SweepDirection.Clockwise;                               //which will complete the remaining circumfrence and end
                finalQuart.IsStroked = true;                                                        // at (finalPointX, finalPointY)
                finalQuart.Size = new Size(radius, radius);
                fig.Segments.Add(finalQuart);                                                       //draw this arc

                //Now one line segment, arcs for the circumfrence - all that is done
                //let's draw a line segment from the end of the sector/arcs back to the
                //center of the circle

                LineSegment lastLine = new LineSegment(new Point(radius, radius), true);
                fig.Segments.Add(lastLine);

                //Now take these figures and add this to the path geometry
                //then add the path geometry to the path's data property
                pg.Figures.Add(fig);
                    path.SetValue(Path.DataProperty, pg);
            }));
            
        }

        /// <summary>
        /// Class Constructor
        /// </summary>
        public CircularProgessChart() {
            InitializeComponent();
            val = 0.1;
        }

        /// <summary>
        /// Loaded event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_Loaded_1(object sender, RoutedEventArgs e) {
            drawSector(val);
        }

        /// <summary>
        /// Size changed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_SizeChanged_1(object sender, SizeChangedEventArgs e) {
            this.Height = this.Width;
            drawSector(val);
        }
    }
}
